#include "unreliable.h"

/* Put the prototypes here for any functions here which you want to be able
   to use by both client.c and server.c. */


// the UDP port users will be connecting to on server
#define SERVERPORT "6666"
//#define DEBUG_SHARED

#define P(string){\
  write(1, string, strlen(string));\
}\

#define SYN 0x80
#define ACK 0x20
#define FIN 0x40

typedef struct{
	int SeqNum;
	char Flags;
	char Character;
	char padding;
	char Checksum;
}RCPacket;

#define PB(thing){\
	char buff[100]={0};\
	for(int i = 0; i < sizeof(RCPacket); i++){\
		char temp[10]={0};\
		sprintf(temp, "%hhu ", (unsigned)thing[i]);\
		strcat(buff, temp);\
	}\
	strcat(buff, "\n");\
	P(buff);\
}\

#define PREPARING     0
#define CONNECTING    1
#define CONNECTED     2
#define DISCONNECTED  3

typedef struct{
	char State;
	int Socket;
	const struct sockaddr* HostAddress;
	socklen_t HostAddrLen;

	int SeqNum;
}RCConnection;

/*--- Functions -------------------------------------------------------*/
int isErrorPacket(RCPacket* packet){
	int sum = 0;
	char* bytes = (char*)packet;
	for(int i = sizeof(RCPacket); i--; sum += bytes[i]);

	if(sum > 0) return 0;
	return 1;
}
/*---------------------------------------------------------------------*/
char computeChecksum(RCPacket packet){
	char* bytes = (char*)&packet;
	char sum = 0;

	// sum all the bytes, not counting the checksum
	for(int i = sizeof(RCPacket) - 1; i--;){
		sum += bytes[i];
	}

	return sum;
}
/*---------------------------------------------------------------------*/
int Recieve(RCConnection* con, RCPacket* packet){

	ssize_t bytes = recvfrom(
		con->Socket,
		packet,
		sizeof(RCPacket),
		0,
		(struct sockaddr*)con->HostAddress,
		&con->HostAddrLen
	);

	if(bytes){
		// compute the check sum, see if it matches the packet's
		char sum = computeChecksum(*packet);

		if(sum == packet->Checksum){
			// checksum is ok!
			P("RECV: ");PB(((char*)packet));
			return 1;
		}
		else{

		}
	}
	// something went wrong fill with 0
	bzero(&packet, sizeof(RCPacket));

	return 0;
}
/*---------------------------------------------------------------------*/
int ReceivePacType(RCConnection* con, RCPacket* packet, char type){
	int res = Recieve(con, packet);

	if(res){
		if(packet->Flags == type) return 1;
	}

	return 0;
}
/*---------------------------------------------------------------------*/
void Send(RCConnection* con, char flags, char c){
	RCPacket packet = {
		con->SeqNum++, // increment the connection's seqnum
		flags,
		c,
		0, // checksum will be computed below
		0
	};

	char* pc = (char*)&packet;
	P("SEND: ");PB(pc);

	packet.Checksum = computeChecksum(packet);

	int res = unreliable_sendto(
		con->Socket,
		&packet,
		sizeof(RCPacket),
		0,
		con->HostAddress,
		con->HostAddrLen
	);
}
/*---------------------------------------------------------------------*/
RCConnection Connect(int socket, const struct sockaddr* dest_addr){
	
	RCConnection connection = {
		CONNECTING, // state
		socket,    // connection socket
		dest_addr,
		sizeof(struct sockaddr),
		0
	};

	Send(&connection, SYN, '\0'); // inititiate the connection
	RCPacket response = {0};

	// get a response from the other host
	Recieve(&connection, &response);

	char synack = (SYN|ACK);
	if(response.Flags == synack){
		// good! we got the syn ack reponse we were looking for
		Send(&connection, (char)(ACK), '\0');
		connection.State = CONNECTED; // connection established
		return connection;
	}
	else{
		connection.State = DISCONNECTED;
	}

	return connection;
}
/*---------------------------------------------------------------------*/
RCConnection Listen(int socket, const struct sockaddr* dest_addr){
	RCConnection connection = {
		PREPARING, // state
		socket,    // connection socket
		dest_addr,
		sizeof(struct sockaddr),
		0
	};

	RCPacket response = {0};

	// get a response from the other host
	Recieve(&connection, &response);
	connection.State = CONNECTING;

	if(response.Flags == (char)SYN){

 		// good! we got the syn reponse we were looking for
		Send(&connection, (char)(SYN|ACK), '\0');

		// continue repeat while data recieved is corrupt
		bzero(&response, sizeof(RCPacket));

		// get a response from the other host
		Recieve(&connection, &response);

		if(response.Flags == (char)ACK){
			// sweet we are connected!
			connection.State = CONNECTED;
			return connection;
		}
	}
	else{
		connection.State = DISCONNECTED;
	}

	return connection;
}
